package second_assignment;

import java.util.LinkedList;
import java.util.Random;
import java.util.Vector;

import first_assignment.Consts_1st.*;
import first_assignment.*;
import fourth_assignment.Request;

/**
 * The manager controlling client requests, and allocations.
 * 
 * @author Peretz Oded, Castel Maayan, Farchy Ofir.
 */
public class Manager {
	double p1 = Consts_2nd.p1;
    double p2 = Consts_2nd.p2;
    double p3 = Consts_2nd.p3;
    int M1 = Consts_2nd.M_Library;
    int M2 = Consts_2nd.M_Recorded;
    int M3 = Consts_2nd.M_RecentlyBroadcast;
    int a1 = Consts_2nd.a1;
    int a2 = Consts_2nd.a2;
    int a3 = Consts_2nd.a3;
    private Random generator = new Random();
    DistributionCreator dc1;
    DistributionCreator dc2;
    DistributionCreator dc3;
    Vector<DistributionCreator> distCreator;

    Vector<LinkedList<Client>> Departure = new Vector<LinkedList<Client>>();
    public ClusterType[] clusters = new ClusterType[Consts_2nd.NumberOfNonRootClusters + 1];
    SystemInitialization initializer;
    
    int movieArrayIndex;
    int movieType;
    
    /**
     * C'tor
     * @param p1
     * @param p2
     * @param p3
     * @param M1
     * @param M2
     * @param M3
     * @param a1
     * @param a2
     * @param a3
     */
    public Manager(Vector<Double> probability, Vector<Integer> moviesAmount, Vector<Integer> popularityDistrib) {
    	p1 = probability.get(0);
        p2 = probability.get(1);
        p3 = probability.get(2);
        M1 = moviesAmount.get(0);
        M2 = moviesAmount.get(1);
        M3 = moviesAmount.get(2);
        a1 = popularityDistrib.get(0);
        a2 = popularityDistrib.get(1);
        a3 = popularityDistrib.get(2);
        dc1 = new DistributionCreator();
        dc1.InitMovieSet(a1, M1);
        dc2 = new DistributionCreator();
        dc2.InitMovieSet(a2, M2);
        dc3 = new DistributionCreator();
        dc3.InitMovieSet(a3, M3);
        
        distCreator = new Vector<DistributionCreator>(3);
        distCreator.add(0, dc1);
        distCreator.add(1, dc2);
        distCreator.add(2, dc3);
        
        // create Root
        clusters[0] = new ClusterType(Consts_2nd.RootDiskCount,
        					(int)Math.floor(((double)Consts_2nd.BaseStreamLineCount / 
        					(1 + 1.5 * Consts_2nd.Rho))));

        for (int i = 1; i < 4; ++i){
            clusters[i] = new ClusterType(Consts_2nd.ClusterDiskCount,
					(int)Math.floor(((double)Consts_2nd.BaseStreamLineCount /
					(1 + Consts_2nd.Rho))));
        }

        // create clusters (mid and pop)
        // 1- mid 4-5 pop    reg1
        // 2- mid 6-7 pop    reg2
        // 3- mid 8-9 pop    reg3
        for (int i = 4; i < 10; ++i){
            clusters[i] = new ClusterType(Consts_2nd.ClusterDiskCount, Consts_2nd.BaseStreamLineCount);
        }
        
        for (int i = 0; i < probability.size(); ++i) {
        	System.out.println("For M(" + (i + 1) + "):");
        	System.out.println("Ammount of movies: " + moviesAmount.get(i).toString() +
        			", Group probability: " + probability.get(i).toString() +
        			", With a (" + popularityDistrib.get(i).toString() + "," +
        			(100 - popularityDistrib.get(i)) + ") distribution.");
        }
        
        // creating the system (the F vector creation part)
        InitDB(moviesAmount, probability, popularityDistrib);
    }
    
    public void InitDB(Vector<Integer> moviesAmount, Vector<Double> probability, Vector<Integer> popularityDistrib)
    {
        // creating the system (the F vector creation part)
        initializer = new SystemInitialization(moviesAmount, probability, popularityDistrib, distCreator);

        initializer.StoreCopies(clusters);
    }
	
    public Vector<Movie> getLibraryArray()
    {
    	return initializer.getFLibrary();
    }
    public Vector<Movie> getRecordedArray()
    {
    	return initializer.getFRecorded();
    }
    public Vector<Movie> getRecentArray()
    {
    	return initializer.getFRecentlyBroadcast();
    }

    public int[] getLibraryCopiesArray()
    {
    	return initializer.getLibraryCopies();
    }
    public int[] getRecordedCopiesArray()
    {
    	return initializer.getRecordedCopies();
    }
    public int[] getRecentCopiesArray()
    {
    	return initializer.getRecentlyBroadcastCopies();
    }

    /**
     * 
     * @return a movie id according to p and M 
     */
    public String getMovie() {
        int requestType = generator.nextInt(100);
        //int movieArrayIndex;
        if ((requestType >= 0) && (requestType < p1*100)) {
        	movieArrayIndex = M1 - dc1.getRandomNumber(M1);
        	movieType = 0;
        	return getLibraryArray().get(movieArrayIndex).movieName;
        }
        else if ((requestType >= p1*100) && (requestType < (p1+p2)*100)) {
        	movieArrayIndex = M2 - dc2.getRandomNumber(M2);
        	movieType = 1;
        	return getRecordedArray().get(movieArrayIndex).movieName;
        }
        else {
        	movieArrayIndex = M3 - dc3.getRandomNumber(M3);
        	movieType = 2;
        	return getRecentArray().get(movieArrayIndex).movieName;
        }
    }

    /**
     * 
     * @return region number 
     */
    public int getRegion() {
        return generator.nextInt(3);
    }

    /**
     * 
     * @return the next arrival time interval with exponential distribution
     */
    public double getNextArriaval() {
        double uni = generator.nextDouble();
        
        double nextArriaval =  -(Math.log(uni))/(Consts_2nd.Lambda);
        return nextArriaval;
    }

    /**
     * release the movie copy
     * @param clusterId
     * @param diskNumber
     * @param movieId
     */
    public void ReleaseCopy(int clusterId, int diskNumber, String movieName){
        clusters[clusterId].ReleaseMovie(movieName, diskNumber);
    }

    /**
     * 
     * @param movieId
     * @param region
     * @param clientId
     * @return
     */
    public Client OccupyMovie(String movieName, int region, int clientId){
        int clusterId = Consts_2nd.ePB_UNINIT;
        int diskNum = Consts_2nd.ePB_UNINIT;

        diskNum = clusters[(region*2)+4].OccupyMovie(movieName);
        if (diskNum >= 0){
            clusterId = (region*2)+4;
        } else {
	        diskNum = clusters[(region*2)+5].OccupyMovie(movieName);
	        if (diskNum >= 0){
	            clusterId = (region*2)+5;
	        } else {
		        diskNum = clusters[region+1].OccupyMovie(movieName);
		        if (diskNum >= 0){
		            clusterId = region+1;
		        } else {
		            diskNum = clusters[0].OccupyMovie(movieName);
		            if (diskNum >= 0){
		                clusterId = 0;
		            } else {
		            	// Client got rejected.
		            	return null;
		            }
		        }
	        }
        }
        region = (clusterId == 0) ? Consts_2nd.eINVALID: region;
        return new Client(clientId, clusterId, diskNum, region, movieName);
    }
    

    public int AddCopyToRoot(Movie movie){
    	return clusters[0].AddMovie(movie.movieName);
    }
    
	public boolean AddNewMovie(Movie newMovie){
		return initializer.AddMovieToClusters(newMovie, clusters);
	}
	

    /**
     * simulation of 24 hours
     */
    public int[] ClientsManagment(LinkedList<Request> requestList) {
    	int[] retVals = new int[3];
        double nextArriavalInterval = getNextArriaval(); 
        int clientsCounter = 0;
        int rejectedClients = 0;
        int servedFromRootClients=0;
        Request newRequest;
        int[] requested = new int[3];
        int[] rejected = new int[3];

        for(int i = 0; i < Consts_2nd.SecondsPerDay; ++i) {
            if ((i % 3600) == 0) { System.out.print("-*"); }
        	if (i >= Consts_2nd.ClientStayInSeconds) {
                if (Departure.size() > 0) {
            		LinkedList<Client> deprtr = Departure.get(0);
            		while (deprtr.size() > 0 && deprtr.getFirst() != null) {
                    	ReleaseCopy( deprtr.getFirst().getClusterId(), deprtr.getFirst().getDiskNumber(),
                    	        deprtr.getFirst().getMovieName());
                    	//System.out.println("Time = " + i +", Client " + deprtr.getFirst().getClientId() +" leaves");
                    	deprtr.removeFirst();
            		}
                	Departure.remove(0);
                }
        	}

        	LinkedList<Client> deprtr = new LinkedList<Client>();
            while ((i == (int)Math.floor(nextArriavalInterval)) && (i < Consts_2nd.ArrivalsOkInSeconds)) {
            	Client newClient = null;
                nextArriavalInterval += getNextArriaval();
                // add new client
                String movieName = getMovie();
                int region = getRegion();
                clientsCounter++;
                requested[movieType]++;

                // Fourth assignment - add request to request List
                newRequest = new Request(i, region, movieType, movieArrayIndex);
                requestList.addLast(newRequest);
                
                newClient = OccupyMovie(movieName, region, clientsCounter);
                if (newClient != null){
//                    System.out.println("Time = " + i +", Client " + newClient.getClientId() + " from R" +
//                            (region+1) + " arrives, requested file: " + movieName + " serviced form disk: " +
//                            newClient.getDiskNumber() + "(in C" +newClient.getClusterId() + ")");
                    if (0 == newClient.getClusterId()) { servedFromRootClients++; }
                }
                else 
                {
//                    System.out.println("Time = " + i +", Client " + clientsCounter + " from R" + (region+1) +
//                            " arrives, requested file: " + movieName + " , rejected");
                    rejectedClients++;
                    rejected[movieType]++;
                    continue;
                }
                deprtr.addLast(newClient);
            }

            if (i >= Consts_2nd.ClientStayInSeconds) {
            	Departure.add(Consts_2nd.ClientStayInSeconds - 1, deprtr);
            } else {
            	Departure.add(i, deprtr);
            }
        }
        System.out.println();
        System.out.println("Number of Clients: "+clientsCounter+" of which "+rejectedClients+" rejected, and " + servedFromRootClients + " serviced from root.");
        System.out.println("Requests: (Library-" + requested[0]+ ", Recorded-"+requested[1]+", Recently_Broadcast-"+ requested[2]+")");
        System.out.println("Rejects: (Library-" + rejected[0]+ ", Recorded-"+rejected[1]+", Recently_Broadcast-"+ rejected[2]+")");
        System.out.println("Rejected clients percentage is: " + ((double)rejectedClients/((double)clientsCounter))*100);
        System.out.println("Serviced clients percentage is: " + ((1.0 - (double)rejectedClients/((double)clientsCounter))*100));

        retVals[0] = clientsCounter;
        retVals[1] = rejectedClients;
        retVals[2] = servedFromRootClients;
        return retVals;
    }
    
    public void PrintStats(String prefix) {
        System.out.println("*******************************" + prefix + "*******************************");
        int countCopies = 0;
        System.out.print(prefix + " (");
        for (ClusterType cluster : clusters) {
            System.out.print(" <" + cluster.AmountOfCopies() + ">");
            countCopies += cluster.AmountOfCopies();
        }
        System.out.println(" )");
        System.out.println(prefix + " Amount of copies in the system is: " + countCopies);
    }

    public Vector<Movie> CreateNewSetOfMovies(int numOfNewMovies, String prefix, int type) {
        if (0 >= numOfNewMovies) { return null; }
        Vector<Movie> tempList = new Vector<Movie>(numOfNewMovies);
        for (int i=0;  i < numOfNewMovies; ++i) {
            Movie newMovie = new Movie(0, prefix + "." + i);
            newMovie.movieType = type;
            tempList.add(i, newMovie);
        }
        return tempList;
    }

    /**
	 * @return the Library Distribution Class
	 */
	public DistributionCreator getLibraryDistributionClass() {
		return dc1;
	}

	/**
	 * @return the Recorded Distribution Class
	 */
	public DistributionCreator getRecordedDistributionClass() {
		return dc2;
	}

	/**
	 * @return the Recently Broadcast Distribution Class
	 */
	public DistributionCreator getRecentlyBroadcastDistributionClass() {
		return dc3;
	}

	public class Client {
        int clientId;
        int clusterId;
        int diskNumber;
        int region;
        String movieName;

        public Client(int ctId, int crId, int dNum, int reg, String name) {
            clientId = ctId;
            clusterId = crId;
            diskNumber = dNum;
            region = reg; 
            movieName = name;
        }

        public String getMovieName() {
            return movieName;
        }
        
        public int getClientId() {
            return clientId;
        }

        public int getClusterId() {
            return clusterId;
        }

        public int getDiskNumber() {
            return diskNumber;
        }

        public int getRegion() {
            return region;
        }
    }

	public int[] RemoveMovieFromRegion(int region, String movieCopyToDel) {
		int cluster = -1;
		int disk = -1;
		int[] retVal = new int[2];
		
		switch (region){
			case 0:
				cluster = 5;
				disk = clusters[5].DeleteMovieCopy(movieCopyToDel);
				if (disk == -1){
					disk = clusters[4].DeleteMovieCopy(movieCopyToDel);
					cluster = 4;
				}
				if (disk == -1){
					disk = clusters[1].DeleteMovieCopy(movieCopyToDel);
					cluster = 1;
				}
				break;
			case 1:
				cluster = 7;
				disk = clusters[7].DeleteMovieCopy(movieCopyToDel);
				if (disk == -1){
					disk = clusters[6].DeleteMovieCopy(movieCopyToDel);
					cluster = 6;
				}
				if (disk == -1){
					disk = clusters[2].DeleteMovieCopy(movieCopyToDel);
					cluster = 2;
				}
				break;
			case 2:
				cluster = 9;
				disk = clusters[9].DeleteMovieCopy(movieCopyToDel);
				if (disk == -1){
					disk = clusters[8].DeleteMovieCopy(movieCopyToDel);
					cluster = 8;
				}
				if (disk == -1){
					disk = clusters[3].DeleteMovieCopy(movieCopyToDel);
					cluster = 3;
				}
				break;
				
		}
				
		retVal[0] = cluster;
		retVal[1] = disk;
		return retVal;
	}

	public boolean AddCopy(int cluster, int disk, Movie movie) {
		return clusters[cluster].AddMovie(movie, disk);
	}

    /**
     * @param string
     */
    public int DeleteAllMovieCopies(String movieName) {
        int countCopies = 0;
        for (int i=0; i <= Consts_2nd.NumberOfNonRootClusters; ++i) {
            while (clusters[i].DeleteMovie(movieName)) {
                ++countCopies;
            }
        }
        return countCopies;
    }
}



